//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: Transaction.cs
//
//  Description: Class that provides a generic transaction object.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;

namespace Chili.Opf3.Storages
{
	/// <summary>
	/// Class that provides a generic transaction object. It is created by a 
	/// class that implements <see cref="ITransactionStorage">ITransactionStorage</see>.
	///	</summary>
	///	<remarks>
	/// <para>
	/// This class provides a transaction object that is created by transactional storages.
	/// Those are storages that implement the <see cref="ITransactionStorage">ITransactionStorage</see> 
	/// interface.
	/// </para>
	/// <para>
	/// Transaction inherits the <see cref="IDisposable">IDisposable</see> interface. That is done
	/// to automatically roll back a transaction if not committed.
	/// </para>
	/// <para>
	/// The class provides methods to roll back or commit a transaction. It is recommended to use
	/// those methods instaead of the one provides by the <see cref="ObjectContext">ObjectContext</see>
	/// to rollback or commit a transaction.
	/// </para>
	///	</remarks>
	///	<example>
	/// <example>
	/// The following example shows how to work with Transaction. 
	/// <code lang="CS">
	/// // Creates a new ObjectContext that uses an MsSql Server as storage.
	/// ObjectContext context = new ObjectContext(new MsSqlStorage("sa", "", 
	///     "localhost", "application"));
	/// 
	/// // Other code...
	///
	/// try
	///	{
	///     // Starts a transaction on the context.
	///     // The using statement can be used here, because Transaction implements 
	///     // the IDisposable interface.
	///     using(Transaction t = context.StartTransaction())
	///     {
	///         // Tries to save an object.
	///			context.PersistChanges(user);
	///         // The save was successful, let's commit now.
	///			t.Commit();
	///     }
	///	}
	///	catch (StorageException ex)
	///	{
	///     // We got an exception. Just print the message.
	///	    Console.WriteLine("An exception happened.");
	///	}
	/// </code>
	/// </example>
	/// </example>
	public abstract class Transaction : IDisposable
	{
		private Dictionary<ObjectInfo, List<ObjectInfo>> _objectCache;
		private string _id = Guid.NewGuid().ToString();

		/// <summary>
		/// Creates a new instance of the <see cref="Transaction">Transaction Class</see>.
		/// </summary>
		protected Transaction()
		{
		}

		/// <summary>
		/// Fired if the transaction had been committed in any way.
		/// </summary>
		public event EventHandler<EventArgs> TransactionCommit;

		/// <summary>
		/// Fired if the transaction had been rolled back in any way.
		/// </summary>
		public event EventHandler<EventArgs> TransactionRollback;

		/// <summary>
		/// Needs to be invoked each time a transaction is committed. This method
		/// fires the TransactionCommit event.
		/// </summary>
		/// <param name="e">The EventArgs for the event.</param>
		protected virtual void OnTransactionCommit(EventArgs e)
		{
			if (TransactionCommit != null)
				TransactionCommit(this, e);
		}

		/// <summary>
		/// Needs to be invoked each time a transaction is committed. This method
		/// fires the TransactionCommit event.
		/// </summary>
		/// <param name="e">The EventArgs for the event.</param>
		protected virtual void OnTransactionRollback(EventArgs e)
		{
			if (TransactionRollback != null)
				TransactionRollback(this, e);
		}

		/// <summary>
		/// Commits the current transaction.
		/// </summary>
		public virtual void Commit()
		{
			// Reset the cache of the transaction objects.
			_objectCache = null;

			// Fire the event.
			OnTransactionCommit(EventArgs.Empty);
			// Log the commit of a transaction.
			Tracing.Current.TraceStorageIO(string.Format("Committed open transaction with ID = {0}.", this.ID));
		}

		/// <summary>
		/// Rolls the current transaction back.
		/// </summary>
		public virtual void Rollback()
		{
			if (_objectCache != null)
			{
				// Roll all objects in the cache back and destroy it afterwards.
				foreach(KeyValuePair<ObjectInfo, List<ObjectInfo>> kv in _objectCache)
				{
					RollbackObjectInfo(kv.Key);
				}
				_objectCache = null;
			}

			// Fire the event.
			OnTransactionRollback(EventArgs.Empty);
			// Log the rollback of a transaction.
			Tracing.Current.TraceStorageIO(string.Format("Rolled open transaction back with ID = {0}.", this.ID));
		}

		/// <summary>
		/// Adds the current <see cref="Opf3.ObjectInfo">ObjectInfo</see> object. This is required to roll
		/// the state of the persistent back during a roll back.
		/// </summary>
		/// <param name="oi"><see cref="Opf3.ObjectInfo">ObjectInfo</see> that is backed up.</param>
		internal void AddObjectInfoInternal(ObjectInfo oi)
		{
			this.AddObjectInfo(oi);
		}

		/// <summary>
		/// Adds the current <see cref="Opf3.ObjectInfo">ObjectInfo</see> object. This is required to roll
		/// the state of the persistent back during a roll back.
		/// </summary>
		/// <param name="oi"><see cref="Opf3.ObjectInfo">ObjectInfo</see> that is backed up.</param>
		/// <returns>Whether the <see cref="Opf3.ObjectInfo">ObjectInfo</see> has been added.</returns>
		protected virtual void AddObjectInfo(ObjectInfo oi)
		{
			// Check if we have to create the cache.
			if (_objectCache == null)
				_objectCache = new Dictionary<ObjectInfo, List<ObjectInfo>>();
			
			// Check if the current ObjectInfo has already a key in the dictionary.
			List<ObjectInfo> ois = null;
			if (!_objectCache.TryGetValue(oi, out ois))
			{
				// Create a list for the object infos and
				// add the item to the cache.
				ois = new List<ObjectInfo>();
				_objectCache.Add(oi, ois);
			}

			// Add the curent version to the list.
			ois.Add((ObjectInfo)oi.Clone());
		}

		/// <summary>
		/// Rolls the <see cref="ObjectInfo">ObjectInfo</see> back to the last version.
		/// </summary>
		/// <param name="oi">The <see cref="ObjectInfo">ObjectInfo</see> object that rolled back.</param>
		protected virtual void RollbackObjectInfo(ObjectInfo oi)
		{
			if (_objectCache == null)
				return;

			// Get the list of items.
			List<ObjectInfo> ois = null;
			if (!_objectCache.TryGetValue(oi, out ois))
				return;

			// Restore and remove the item.
			oi.Restore(ois[ois.Count - 1]);
			ois.RemoveAt(ois.Count - 1);
		}

		/// <summary>
		/// Returns the ID of the transaction.
		/// </summary>
		public string ID
		{
			get { return _id; }
		}

		#region Abstract Members

		/// <summary>
		/// Returns whether the transaction is active.
		/// </summary>
		public abstract bool Active { get; }

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Disposes this instance of the transaction object. If the
		/// transaction hasn't been committed, it is rolled back automatically.
		/// </summary>
		public virtual void Dispose()
		{
			if (this.Active)
				this.Rollback();
		}

		#endregion
	}
}
